//
// Created by Administrator on 2025/9/15.
//

#ifndef C_LEARN_CPPEXC_H
#define C_LEARN_CPPEXC_H

#include <iostream>
#include <iomanip>


class CppExC {
    //  类型增强
public:
    //  严格类型检查
    void StrictTypeCheck(){
        const int a = 100;
        const int *pa = &a;
        //  在C中可以，C++直接编译报错
        //  int *pb = pa;
    }

    //  真正的枚举
    //  C++枚举变量只能用被枚举出来的元素初始化
    enum season{
        SPR = 0,
        SUM,
        AUT,
        WIN
    };

    void TrueEnum(){
        enum season s = SPR;
        //  C++枚举不支持整型赋值，会编译错误
        //  s = 0;
    }

    //  表达式的值可被赋值
    //  C中表达式通常不能作为左值，即不可被赋值，C++中某些表达式可以被赋值
    void ExpressionAssignment(){
        int a, b = 5;
        (a = b) = 10;
        //  a = 10, b = 5
        //  即 （a = b） ==> a, (a = b) = 10 ==> a = 5;
        std::cout << "a = " << a << ", b = " << b << std::endl;
        //  a = 10, b = 200
        //  即 （a < b ? a : b） ==> b, (a < b ? a : b) = 200 ==> b = 200;
        (a < b ? a : b) = 200;
        std::cout << "a = " << a << ", b = " << b << std::endl;
    }

    //  输入与输出
public:
    //  设置域宽及位数
    void SetTextWidthAndNumberCount(){
        //  setw(n)设置下一个输出项的最小字段宽度, 是一次性的
        std::cout << std::setw(5) << 'a' << std::endl
        << std::setw(5) << 100 << std::endl
        //  setprecision(n)配合 fixed，表示小数点后保留 2 位, 设置后持续有效，直到再次更改
        //  setiosflags(ios::fixed)使用定点表示法（fixed-point notation），即总是显示小数点后固定位数, 设置后持续有效，直到用 resetiosflags 或 unsetf 清除
        << std::setprecision(2) << std::setiosflags(std::ios::fixed) << 120.00 << std::endl;
    }

    //  输出十进制，十六进制，八进制。默认输出十进制的数据
    void NumberBase(){
        int i = 123;
        std::cout << i << std::endl;
        std::cout << std::dec << i << std::endl;
        std::cout << std::hex << i << std::endl;
        std::cout << std::oct << i << std::endl;
        std::cout << std::setbase(16) << i << std::endl;
    }

    //  设置填充符
    void SetFiller(){
        std::cout << std::setw(10) << 1234 << std::endl;
        //  setfill('0')：设置填充字符为 '0'
        std::cout << std::setw(10) << std::setfill('0') << 1234 << std::endl;
        //  setiosflags(ios::left)：设置左对齐
        std::cout << std::setw(10) << std::setfill('0') << std::setiosflags(std::ios::left) << 1234 << std::endl;
        std::cout << std::setw(10) << std::setfill('-') << std::setiosflags(std::ios::right) << 1234 << std::endl;
    }

    //  函数重载
public:
    //  C++能够通过函数重名来达到简化编程
    //  C不可以函数重名

    //  函数重载匹配原则：
    //  1. 严格匹配，找到则调用
    //  2. 通过隐式转换寻求一个匹配，找到则调用
    //  PS: C++允许int->long和double， double->int和float的隐式类型转换，遇到这种情况，则会引起二义性。
    //  解决方法是：在调用时显式转化
    //  例如有重载函数print(double a);print(long a);
    //  int a; print(a);则会不知道该调用那个，需要print((long)a);

    //  重载底层实现
    //  C++利用name mangling(倾轧)技术，来该名函数名，区分参数不同的同名函数
    //  实现原理：用v-c-i-f-l-d表示void char int float long double及其引用
    //  void func(char a);  //  func_c(char a);
    //  void func(char a, int b, double c); //  func_cid(char a, int b, double c);

    //  extern "C"
    //  name mangling 发生在两个阶段，.cpp编译阶段，和.h的声明阶段。
    //  只有两个阶段同时进行，才能匹配调用
    //  C++完全兼容C,那就面临着完全兼容C的类库，由于.c文件的类库文件中函数名，并没有发生name mangling行为，而我们在包含.c文件对应的.h文件时，.h文件要发生name mangling行为，
    //  因而会发生在链接的时候的错误。
    //  C++为了避免上述错误的发生，重载了关键字extern，只需要在要避免name mangling的函数前，加上extern "C"即可，如有多个则使用extern “C" {};

    //  引用
public:
    //  引用的本质是指针，C++对裸露的内存地址(指针)作了一次包装，又取得的指针的优良特性，所以再对引用取地址，建立引用的指针没有意义。
    //  可以定义指针的引用，但不能定义引用的引用。
    //  可以定义指针的指针，但不能定义引用的指针(int a; int &r = a; int&* pr = &r);
    //  原因很简单，引用是一个别名，实际使用就是等同于原变量，对其取引用等同于对原变量取引用，得到一个以及指针。（虽然引用本质是一个指针，但是因为有封装，对其操作不可以当作指针）

    //  可以定义指针数组，但不能定义引用数组，可以定义数组引用。
    //  int a, b, c;
    //  int* parr[] = {&a, &b, &c};
    //  int& rarr[] = {a, b ,c};    // error, 引用就是一个别名,所以引用数组不就是一个数组吗，不需要加&
    //  int arr[] = {1, 2, 3}
    //  int (&rarr)[3] = arr;

    //  常引用
    //  const对象的引用必须是const的，将普通引用绑定到const对象是不合法的。
    //  const引用可使用相关类型的对象(常量， 非同类型的变量(可以隐式转换的)或表达式(表达式结果的类型必须是可以转换的))初始化。

    //  尽可能使用const的原因
    //  1. 使用const可以避免无意修改数据的编程错误。
    //  2. 使用const可以处理const和非const实参，否则将只能接受非const数据。
    //  3. 使用const引用，可使函数能够正确的生成并使用临时变量（即实参与引用参数不匹配时，就会生成临时变量）
    //  临时变量---即用完就丢的变量，相当于_,不会保存其引用，相当于func("hello")的"hello"， 这个实参用完就不可以再用了(因为没有其引用了)。

    //  new 和 delete
public:
    //  返回值
    //  内存申请失败会抛出异常
    int MallocMemory() {
        try {
            int *p = new int[10];
            delete []p;
        } catch (const std::bad_alloc& e) {
            return -1;
        }
        //  内存申请失败不抛出异常版本
        int *q = new (std::nothrow)int[10];
        if(q == NULL)
            return -1;
        delete []q;
        return 0;
    }


    //  内联函数(inline function)
public:
    //  C中有宏函数的概念，宏函数的特点是内嵌到调用代码中区，避免了函数调用的开销。但由于宏函数的发生在预处理阶段，缺失了语法检测和有可能带来语意差错。
    //  C++提供了inline关键字，实现真正的内嵌。

    //  宏函数
    //  优点：内嵌代码，避免压栈与出栈的开销
    //  缺点：代码替换，易使代码体积变大，易产生逻辑错误，无类型检查。

    //  内联函数
    //  高度抽象，避免重复开发，类型检查

    //  优点：避免调用时的额外开销(入栈与出栈操作)
    //  代价：由于内联函数的函数体在代码段中会出现多个“副本”，因此会增加代码段的空间、
    //  本质：以牺牲代码段空间为代价，提高程序的运行时间的效率。
    //  适用场景：函数体很“小”，且被“频繁”调用、

    /*
        内联函数的限制与注意事项
        1.编译器决定是否内联：inline 只是一个建议，编译器可以忽略。复杂的函数（如包含循环、递归、大量代码）通常不会被内联。
        2.可能导致代码膨胀：如果函数体较大且被频繁调用，内联会导致目标代码体积显著增加。
        3.定义通常放在头文件中：因为内联函数在编译时需要知道函数体，所以通常将内联函数的定义放在头文件（.h 或 .hpp）中，以便多个源文件包含。
        4.不能包含某些复杂结构：如 static 变量、递归调用等可能阻止内联。
     */


    //  类型强转(type cast)
public:
    //  静态类型转换
    //  基本数据类型的转换
    //  静态转换用于类层次结构中父类和子类之间或引用的转换。
    void staticCast(){
        //  static_cast<要转换的类型>(变量名或表达式)
        //  基本数据类型转换
        //  整数类型之间的转换
        int num = 97;
        char ch = static_cast<char>(num);
        //  指针与void*之间的转换
        int value = 100;
        int* intPtr = &value;
        void* voidPtr = static_cast<void*>(intPtr);
        int* newIntPtr = static_cast<int*>(voidPtr);
        //  指针与空指针(nullptr)之间的转换
        //  这是在处理泛型指针时的一种常见用法
        int* ptr = nullptr;
        voidPtr = static_cast<void*>(ptr);

        //  从子类转换为父类(Upcasting,安全)
        //  从父类转换为子类(Downcasting, 不安全)
        //  只有当我们确信父类指针实际上指向的是一个子类对象时，才能进行这种转换。
        //  否则，转换后的对象可能无法正常工作，甚至导致未定义行为。
    }

    //  动态转换(dynamic_cast)
    //  专门用于处理多态类型（即包含虚函数的类层次结构）
    //  可以在运行时对类的类型进行检查，确保类型转换的安全性。
    //  相比于静态类型转换，动态转换会在运行时进行检查，并且只有在以下两种情况下使用：
    //  上行转换(Upcasting): 从子类转换为父类
    //  下行转换(Downcasting): 从父类转换为子类，且要求父类中至少有一个虚函数（即是多态类）
    //  dynamic_cast在进行下行转换时，它可以在运行时检查父类指针或引用是否实际指向一个子类对象。
    //  如果转换失败，dynamic_cast会返回nullptr(对于指针类型),或抛出异常(对于引用类型),从而避免不安全的类型转换。
};


#endif